package edu.serg.mbar.ucon.pdp;

import edu.serg.mbar.ucon.meta.*;
import edu.serg.utils.XMLUtils;
import edu.serg.mbar.ucon.interfaces.PolicyEvaluator;
import edu.serg.mbar.ucon.interfaces.AttributeReader;
import edu.serg.mbar.ucon.interfaces.AttributeUpdateManager;
import edu.serg.mbar.ucon.interfaces.ExpressionEvaluator;
import edu.serg.mbar.ucon.interfaces.CombiningAlgorithm;

import org.apache.log4j.Logger;
import org.apache.xpath.*;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Document;
import org.w3c.dom.traversal.NodeIterator;

import javax.xml.parsers.*;
import java.text.SimpleDateFormat;
import java.util.Vector;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;


/**
 * This class is used for evaluating Policies, the policy is evaluated based upon the subject, object
 * right and the Type of policy
 * 
 * @author Sanaullah
 *
 */
public class PolicyEvaluationManager implements PolicyEvaluator {
	
	static Logger log = Logger.getLogger(PolicyEvaluationManager.class);
	private final static String POLICIES_DIR = RegisteredStrings.POLICIES_DIR;
	
	private AttributeReader attribReader;
	private AttributeUpdateManager attribUpdateManager;
	private ExpressionEvaluator exprEvaluator;
	XMLUtils xmlUtils =new XMLUtils();
	
	public PolicyEvaluationManager(AttributeReader atReader,AttributeUpdateManager atUpdateMgr ,ExpressionEvaluator expEval)
	{
		attribReader= atReader;
		exprEvaluator = expEval;
		attribUpdateManager = atUpdateMgr;
	}
	
	private PolicyEvaluationManager(){
		
	}
	
	/**
	 * Function for evaluating a policy returns true if the condition 
	 * stipulated in the policy are met
	 *  
	 * @param object
	 * name of the object for which the policy is going to be evaluated
	 * 
	 * @param subject
	 * @param right
	 * @param policyType
	 * 
	 * @return
	 */
	public boolean evaluatePolicy(String subject, String object, String right, String policyType){
		
//		Calendar cal =Calendar.getInstance();
		long startTime =System.currentTimeMillis();
		boolean policyValidity=false;
		log.debug("evaluating policy for subject '" + subject +"', Object '" + object +"', Right '" + right +"' PolicyType '" + policyType +"'");
		XMLUtils xmlUtils = new XMLUtils();
		
				
		String policyFile = POLICIES_DIR + object +"Policy.xml";
		log.debug("parsing the following policy file =" + policyFile);
		
		String xPath ="//p:PolicySet/p:Policy[@PolicyType='" + policyType + "' and p:Target/p:Subjects/p:Subject/text()='" + subject + "' and p:Target/p:Objects/p:Object/text()='"+ object +"']/p:Condition";
//		String xPath ="//p:PolicySet/p:Policy";
		try
		{
			
					
//			NodeSet conditionNodeSet=xmlUtils.xPathEvaluateToNodeSet(xmlUtils.parseXmlFromFile(policyFile), xPath);
			NodeList conditionNodeList=xmlUtils.xPathEvaluateToNodeList(xmlUtils.parseXmlFromFile(policyFile), xPath);
			
			log.debug("The number of Condition elements within the Policy are "+ conditionNodeList.getLength());
			int totalConditionNodes=conditionNodeList.getLength();
			
			for (int i=0; i < totalConditionNodes ; i++){
				
				policyValidity=evaluateCondition(conditionNodeList.item(i), subject, object, right);
				log.debug("The validity of Condition("+ i +") is " + policyValidity);
				//if one of the policy is validated then set the value of i so that the loop terminates
				if (policyValidity==true)
				{
					break;
				}
			}
								
		}
		catch (Exception ex)
		{
			log.error(ex.toString());
		}
		long finishTime=System.currentTimeMillis();
		SimpleDateFormat dateFormat =new SimpleDateFormat("HH:mm:ss.SSS");
		dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
		log.info("* --> * Elapsed Time: Policy Evaluation completed in [" + (dateFormat.format(new Date (finishTime- startTime))) + "] Time the Validity of the Policy is "+ policyValidity );
		
		return policyValidity;
		
	}
	
	/**
	 * 
	 * This Function evaluates a condition within a policy, 
	 * this function receives a  Node as argument representing 
	 * the condition element of a policy
	 *  
	 * @param conditionNode
	 * a node which represents the condition element within a given policy
	 * 
	 * @return
	 * returns a boolean value to represent whether the condition of the policy is being satisfied or not
	 */
	public boolean evaluateCondition(Node conditionNode, String subject, String object, String right){
		long startTime=System.currentTimeMillis();
		log.debug("* Evaluating Condition");
		boolean conditionValidated =false;
		String nodeName = conditionNode.getNodeName();
		String combiningAlgo = "";
		Vector<Boolean> functionResults = new Vector<Boolean>();
		
		if (nodeName.split(":", 2)[1].equalsIgnoreCase("Condition"))//("Condition"))
		{
			try
			{
//				log.debug("-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*");
//			String xmlOfConditionNode=xmlUtils.xmlFromNodetoString(conditionNode);
//			log.debug(xmlOfConditionNode);
//			log.debug("-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*");
			// get the attribute of the condition node
			NamedNodeMap attributes;
			attributes=conditionNode.getAttributes();
			Node combiningAlgorithmAttribute; // the combining algorithm of the condition
			combiningAlgorithmAttribute=attributes.getNamedItem("CombiningAlgorithm");
			log.debug("Retreiving the combining algorithm attribute of the Condition Element or Node");
			combiningAlgo =combiningAlgorithmAttribute.getNodeValue();
			log.debug("** The retreived combining algorithm for the condition is "+ combiningAlgo );
			
			//TODO: The Combining Algorithm for the results received from different expressions
//			xmlUtils.XMLtoString(conditionNode);
			NodeList expressionNodes = xmlUtils.xPathEvaluateToNodeList(conditionNode, "//p:Condition/p:Expression");
			log.debug("The Number of Expression Nodes within the Condition Node = " + expressionNodes.getLength());
			
			for (int i=0 ; i < expressionNodes.getLength();i++){
				if (expressionNodes.item(i).getNodeName().split(":", 2)[1].equalsIgnoreCase("Expression")){
					Vector<Object> results = exprEvaluator.evaluateExpression(expressionNodes.item(i),subject, object);
//					Object r[];
					for (Object result : results)
					{
						functionResults.add((Boolean) result);
						//functionResults.addAll(result);
					}
					
					}
			}
			
			CombiningAlgorithm combineAlgorithm = (CombiningAlgorithm) Class.forName(combiningAlgo ).newInstance();
			conditionValidated=combineAlgorithm.combine(functionResults);
			}
			catch (Exception e)
			{
				conditionValidated=false;
				log.error(e.getMessage());
				e.printStackTrace();
			}
					
		}
		else
		{
		log.debug("The node received as argument is not a condition node");	
		}
		long finishTime=System.currentTimeMillis();
		SimpleDateFormat dateFormat =new SimpleDateFormat("HH:mm:ss.SSS");
		dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
		log.info("* --> * Elapsed Time: Condition Evaluated in [" + (dateFormat.format(new Date(finishTime-startTime))) + "] Time the Validity of the condition is " + conditionValidated);
		return conditionValidated;
	}

	
//	/**
//	 * 
//	 * This function is responsible for the evaluation of expression item/tag inside the condition tag/item within a policy.
//	 * the function takes an argument node and processes it.
//	 * 
//	 * @param expressionNode
//	 * The node which represents the expression item in the Condition tag/item of a Policy
//	 * 
//	 * @return
//	 */
//	public Vector<Object> evaluateExpression(Node expressionNode, String subject, String object, String right){
//		long startTime=System.currentTimeMillis();
//		String nameOfNode=expressionNode.getNodeName();
//		Vector<Object> resultSet=new Vector<Object>();
//		Vector<Object> attributeValue = new Vector<Object>();
//		Object constantValue= new Object();
//		Vector<Object> expressionResult=new Vector<Object>();
//		Vector<Object> functionArgument = new Vector<Object>();
//		log.info("Evaluating Expression");
//		if (nameOfNode.split(":", 2)[1].equalsIgnoreCase("Expression"))
//		{
//			XMLUtils xmlUtils = new XMLUtils();
////			log.debug("------------------------------------------------------");
////			String xmlOfExpression = xmlUtils.xmlFromNodetoString(expressionNode);
////			log.debug(xmlOfExpression);
////			log.debug("------------------------------------------------------");
////			log.debug("Start evaluating expression");
//		
//			try {
//				String functionID=expressionNode.getAttributes().getNamedItem("FunctionID").getNodeValue();
//			
//				IFunction function = (IFunction) Class.forName(functionID).newInstance();
//			
//				log.info("FunctionID of the expression being evaluated is '"+ functionID +"'");
//				//NodeList childNodes=expressionNode.getChildNodes();
//				//get the child nodes of the the expression element/node
//				NodeList childNodes = xmlUtils.xPathEvaluateToNodeList(expressionNode, "./*");
//				log.debug("Child nodes within New expression Node are " + childNodes.getLength() );
//			
//				int totalChildNodes =childNodes.getLength();
//				for (int i=0; i<totalChildNodes ; i++){
//					String nodeName = childNodes.item(i).getNodeName();
//					log.debug ("Element " + i + " is " + nodeName);
////					log.debug("     ?????");
////					log.debug(xmlUtils.xmlFromNodetoString(childNodes.item(i)));
////					log.debug("     ?????");
//					
//					// determine whether the Node Name contains a prefix or not
//					if (nodeName.contains(":"))
//					{
//						//if contains prefix then remove the prefix by splitting the name into prefix and name and store the name in the variable
//						nodeName= nodeName.split(":",2)[1];
//					}
//					// now based on the name of the node/Element take an appropriate action
//					if ( nodeName.equalsIgnoreCase("ObjectAttribute")) 
//					{
//						String targetOfAttribute=attribReader.resolveTargetOfObjectAttributeNode(childNodes.item(i),subject,object);
//						Node attributeNameNode =xmlUtils.xPathEvaluateToNode(childNodes.item(i), "./p:AttributeName");
//						attributeValue=attribReader.getAttributeValue(targetOfAttribute, xmlUtils.getStringValueOfNode(attributeNameNode));
//						functionArgument.addAll(attributeValue);
//					} 
//					else
//					{
//						if (nodeName.equalsIgnoreCase("Constant"))
//						{
//							NamedNodeMap attributesOfConstant=childNodes.item(i).getAttributes();
//							//Determine the value of the Datatype attribute of the element Constant
//							Node dataTypeOfConstant = attributesOfConstant.getNamedItem("DataType");
//							log.debug("The datatype of the Constant is " + dataTypeOfConstant.getNodeValue());
//
//							// if the Datatype of the Constant is Time then
//							if ((String) dataTypeOfConstant.getNodeValue()=="Time")
//							{
//								SimpleDateFormat timeFormat =new SimpleDateFormat("HHmm");
//								String timeString = xmlUtils.getStringValueOfNode(childNodes.item(i));
//								constantValue=timeFormat.parse(timeString);
//							}
//							else
//							{
//								constantValue =xmlUtils.getStringValueOfNode(childNodes.item(i));
//							}
//							// add the value of the constant to the list of arguments which is to be passed to the function
//							functionArgument.add(constantValue);
//						}
//						else
//						{
//							if (nodeName.equalsIgnoreCase("Expression"))
//							{
//								expressionResult=evaluateExpression(childNodes.item(i),subject,object,right);
//								functionArgument.addAll(expressionResult);
//							}
//						}
//					}
//				}
//				log.debug("* # Calling Function '" + functionID +"' with the vector of Size " + functionArgument.size()  +" # *");
//				Object resultOfFunction =function.evaluate(functionArgument);
//				resultSet.add(resultOfFunction);
//			
//			}catch (Exception e)
//			{
//				log.error(e.getMessage());
//				e.printStackTrace();
//		
//			}
//		}
//		else
//		{
//			log.error("Invalid expression Node");
//		}
//		
//		long finishTime=System.currentTimeMillis();
//		SimpleDateFormat dateFormat =new SimpleDateFormat("HH:mm:ss.SSS");
//		dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
//		log.info("* --> * Elapsed Time: Expression Evaluated in [" + (dateFormat.format(new Date((finishTime -startTime)))) + "] Time");
//				
//		return resultSet;
//		
//	}
	
	
///**
// * The Function is used to determine the Object or the Target the attribute is associated with
// *  
// * @param attributeNode
// * 			The Attribute Node/Element whose target is to be determined
// * @param subject
// * 			The subject for whom the policy is being evaluated
// * @param object
// * 			the object being requested by the subject
// * @param right
// * 			the right the subject wants on the object
// * @return
// * 			The objectID of the attribute i.e the object with which the attribute is associated
// */
//	public String targetOfAttribute(Node attributeNode, String subject, String object, String right){
//		String target="";
////		log.debug("The attribute Node is ");
////		log.debug(xmlUtils.xmlFromNodetoString(attributeNode));
//		NodeList objectIDNodes = xmlUtils.xPathEvaluateToNodeList(attributeNode, "./p:ObjectID");
//		//read only the first node at index 0 in the node set because we are only expecting one objectID Node/Element in the NodeList
//		if (objectIDNodes.getLength()==0){
//			NodeList requestingSubject =xmlUtils.xPathEvaluateToNodeList(attributeNode, "./p:RequestingSubject");
//			//only the first node in the NodeList is being read because we are expecting only one node of RequestingObject in place of the ObjectID element
//			if (requestingSubject.getLength()==0){
//				NodeList requestedObject =xmlUtils.xPathEvaluateToNodeList(attributeNode, "./p:RequestedObject");
//				log.debug("ObjectID of the Attribute is the ID of the OBJECT being requested by the subject");
//				//log.debug(xmlUtils.xmlFromNodetoString(requestedObject.item(0)));
//				//only the first node is being read because we are expecting only one node of RequestedObject inplace of the objectID Element 
//				target=object;
//			} 
//			else {
//				log.debug("ObjectID of the attribute is The ID of the SUBJECT");
//				target=subject;
//			}
//		}
//		else
//		{
//			//In our schema there can be only one ObjectID node/element inside the ObjectAttribute or TargetAttribute Node.
//			//get the first ObjectID element form the NodeList
//			Node objectIDNode =objectIDNodes.item(0);
//			//get the first child of the ObjectID node, which is the text node which contains the text data
//			Node txtNode = objectIDNode.getChildNodes().item(0);
//			//Get the text data from the text node
//			target=txtNode.getNodeValue();	
//		}
//		
//		log.debug("The target of the attribute is "+target);
//		return target;		
//	} 
}
